package com.zh.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zh.blog.common.redis.RedisService;
import com.zh.blog.dao.*;
import com.zh.blog.dto.*;
import com.zh.blog.model.Article;
import com.zh.blog.model.WebsiteConfig;
import com.zh.blog.service.BlogInfoService;
import com.zh.blog.service.PageService;
import com.zh.blog.service.UniqueViewService;
import com.zh.blog.util.BeanCopyUtils;
import com.zh.blog.util.IpUtils;
import com.zh.blog.vo.BlogInfoVO;
import com.zh.blog.vo.PageVO;
import com.zh.blog.vo.WebsiteConfigVO;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

import static com.zh.blog.constant.CommonConst.*;
import static com.zh.blog.constant.RedisPrefixConst.*;
import static com.zh.blog.enums.ArticleStatusEnum.PUBLIC;

/**
 * 博客信息服务
 * @author ZhengHao
 * Created At 2022/7/31.
 **/
@Service
public class BlogInfoServiceImpl implements BlogInfoService {
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private ArticleDao articleDao;
    @Autowired
    private CategoryDao categoryDao;
    @Autowired
    private TagDao tagDao;
    @Autowired
    private MessageDao messageDao;
    @Autowired
    private UniqueViewService uniqueViewService;
    @Autowired
    private WebSiteConfigDao webSiteConfigDao;
    @Autowired
    private RedisService redisService;
    @Autowired
    private PageService pageService;
    @Autowired
    private HttpServletRequest request;

    @Override
    public BlogBackInfoDto getBlogBackInfo() {
        // 查访问量
        Object count = redisService.get(BLOG_VIEWS_COUNT);
        Integer viewsCount = Integer.parseInt(Optional.ofNullable(count).orElse(0).toString());
        // 查询留言量
        Long messageCount = messageDao.selectCount(null);
        // 查询用户量
        Long userCount = userInfoDao.selectCount(null);
        // 查询文章量
        Long articleCount = articleDao.selectCount(new LambdaQueryWrapper<Article>()
                .eq(Article::getIsDelete, FALSE));
        // 查询一周用户量
        List<UniqueViewDto> uniqueViewDtoList = uniqueViewService.listUniqueViews();
        // 查询文章统计
        List<ArticleStatisticsDto> articleStatisticsDtoList = articleDao.listArticleStatistics();
        // 查询分类数据
        List<CategoryDto> categoryDtoList = categoryDao.listCategoryDto();
        // 查询标签数据
        List<TagDto> tagDtoList = BeanCopyUtils.copyList(tagDao.selectList(null), TagDto.class);
        // 查询redis访问量前五的文章
        Map<Object, Double> articleMap = redisService.zReverseRangeWithScore(ARTICLE_VIEWS_COUNT,0,4);
        BlogBackInfoDto blogBackInfoDto = BlogBackInfoDto.builder()
                .articleStatisticsDtoList(articleStatisticsDtoList)
                .tagDtoList(tagDtoList)
                .viewsCount(viewsCount)
                .messageCount(messageCount)
                .userCount(userCount)
                .articleCount(articleCount)
                .categoryDtoList(categoryDtoList)
                .uniqueViewDtoList(uniqueViewDtoList)
                .build();
        if (CollectionUtils.isNotEmpty(articleMap)){
            // 查询文章排行
            List<ArticleRankDto> articleRankDtoList = listArticleRank(articleMap);
            blogBackInfoDto.setArticleRankDtoList(articleRankDtoList);
        }
        return blogBackInfoDto;
    }

    @Override
    public WebsiteConfigVO getWebsiteConfig() {
        WebsiteConfigVO websiteConfigVO;
        // 获取缓存数据
        Object websiteConfig = redisService.get(WEBSITE_CONFIG);
        if (Objects.nonNull(websiteConfig)){
            websiteConfigVO = JSON.parseObject(websiteConfig.toString(),WebsiteConfigVO.class);
        } else {
            // 从数据库中加载
            String config = webSiteConfigDao.selectById(DEFAULT_CONFIG_ID).getConfig();
            websiteConfigVO = JSON.parseObject(config, WebsiteConfigVO.class);
            redisService.set(WEBSITE_CONFIG, config);
        }
        return websiteConfigVO;
    }

    @Override
    public void updateWebsiteConfig(WebsiteConfigVO websiteConfigVO) {
        // 修改网站配置
        WebsiteConfig websiteConfig = WebsiteConfig.builder()
                .id(1)
                .config(JSON.toJSONString(websiteConfigVO))
                .build();
        webSiteConfigDao.updateById(websiteConfig);
        // 删除缓存
        redisService.del(WEBSITE_CONFIG);
    }

    @Override
    public String getAbout() {
        Object value = redisService.get(ABOUT);
        return Objects.nonNull(value) ? value.toString() : "";
    }

    @Override
    public void updateAbout(BlogInfoVO blogInfoVO) {
        redisService.set(ABOUT, blogInfoVO.getAboutContent());
    }

    @Override
    public BlogHomeInfoDto getBlogHomeInfo() {
        // 查询文章数量
        Long articleCount = articleDao.selectCount(new LambdaQueryWrapper<Article>()
                .eq(Article::getStatus, PUBLIC.getStatus())
                .eq(Article::getIsDelete, FALSE));
        // 查询分类数量
        Long categoryCount = categoryDao.selectCount(null);
        // 查询标签数量
        Long tagCount = tagDao.selectCount(null);
        // 查询访问量
        Object count = redisService.get(BLOG_VIEWS_COUNT);
        String viewsCount = Optional.ofNullable(count).orElse(0).toString();
        // 查询网站配置信息
        WebsiteConfigVO websiteConfig = this.getWebsiteConfig();
        // 查询页面图片
        List<PageVO> pageVOList = pageService.listPages();
        // 封装数据
        return BlogHomeInfoDto.builder()
                .articleCount(articleCount)
                .categoryCount(categoryCount)
                .tagCount(tagCount)
                .viewsCount(viewsCount)
                .websiteConfig(websiteConfig)
                .pageList(pageVOList)
                .build();
    }

    @Override
    public void report() {
        // 获取IP
        String ipAddress = IpUtils.getIpAddress(request);
        // 获取访问设备
        UserAgent userAgent = IpUtils.getUserAgent(request);
        // 访问的浏览器
        Browser browser = userAgent.getBrowser();
        // 访问的操作系统
        OperatingSystem operatingSystem = userAgent.getOperatingSystem();
        // 生成唯一用户标识
        String uuid = ipAddress + browser.getName() + operatingSystem.getName();
        String md5 = DigestUtils.md5DigestAsHex(uuid.getBytes());
        // 判断是否访问过
        if (!redisService.sIsMember(UNIQUE_VISITOR,md5)){
            // 统计游客地域分布
            String ipSource = IpUtils.getIpSource(ipAddress);
            if (StringUtils.isNotBlank(ipSource)) {
                ipSource = ipSource.substring(0,2)
                        .replaceAll(PROVINCE,"")
                        .replaceAll(CITY,"");
                redisService.hIncr(VISITOR_AREA, ipSource,1L);
            } else {
                redisService.hIncr(VISITOR_AREA,UNKNOWN,1L);
            }
            // 访问量加1
            redisService.incr(BLOG_VIEWS_COUNT ,1);
            // 保存唯一标识
            redisService.sAdd(UNIQUE_VISITOR,md5);
        }
    }

    /**
     * 查询文章排行
     * @param articleMap 文章信息
     * @return 文章排行
     */
    private List<ArticleRankDto> listArticleRank(Map<Object, Double> articleMap) {
        // 提取文章ID
        List<Integer> articleIdList = new ArrayList<Integer>(articleMap.size());
        articleMap.forEach((key, value) -> articleIdList.add((Integer) key));
        // 查询文章信息
        return articleDao.selectList(new LambdaQueryWrapper<Article>()
                .select(Article::getId, Article::getArticleTitle)
                .in(Article::getId, articleIdList))
            .stream().map(article -> ArticleRankDto.builder()
                .articleTitle(article.getArticleTitle())
                .viewsCount(articleMap.get(article.getId()).intValue())
                .build())
            .sorted(Comparator.comparingInt(ArticleRankDto::getViewsCount).reversed())
            .collect(Collectors.toList());
    }
}